package com.bpmn.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.bpmn.common.constant.BpmnModelConstants;
import com.bpmn.common.constant.FlowableConstants;
import com.bpmn.common.exception.ServiceException;
import com.bpmn.flow.controller.vo.ButtonConfig;
import com.bpmn.flow.listener.AutoApprovalTaskListener;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.engine.ProcessEngine;
import org.flowable.task.service.delegate.TaskListener;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.rmi.ServerException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 模型工具
 *
 * @author rain
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ModelUtils {

    private static final ProcessEngine PROCESS_ENGINE = SpringUtil.getBean(ProcessEngine.class);

    public static BpmnModel xmlToBpmnModel(String xml) throws IOException {
        if (xml == null) {
            throw new ServerException("xml不能为空");
        }
        try {
            InputStream inputStream = new ByteArrayInputStream(StrUtil.utf8Bytes(xml));
            XMLInputFactory factory = XMLInputFactory.newInstance();
            XMLStreamReader reader = factory.createXMLStreamReader(inputStream);
            return new BpmnXMLConverter().convertToBpmnModel(reader);
        } catch (XMLStreamException e) {
            throw new ServerException(e.getMessage());
        }
    }

    /**
     * xml转为bpmnModel
     *
     * @param xmlBytes xml
     */
    public static BpmnModel xmlToBpmnModel(byte[] xmlBytes) throws XMLStreamException {
        ByteArrayInputStream byteArrayInputStream = IoUtil.toStream(xmlBytes);
        XMLInputFactory xif = XMLInputFactory.newInstance();
        XMLStreamReader xtr = xif.createXMLStreamReader(byteArrayInputStream);
        return new BpmnXMLConverter().convertToBpmnModel(xtr);
    }


    public static String bpmnModelToXml(BpmnModel model) {
        return StrUtil.utf8Str(new BpmnXMLConverter().convertToXML(model));
    }

    /**
     * 校验模型
     *
     * @param bpmnModel bpmn模型
     */
    public static void checkBpmnModel(BpmnModel bpmnModel) throws ServerException {
        Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
        checkBpmnNode(flowElements, false);
        List<SubProcess> subProcessList = flowElements.stream().filter(SubProcess.class::isInstance).map(SubProcess.class::cast).collect(Collectors.toList());
        if (!CollUtil.isEmpty(subProcessList)) {
            for (SubProcess subProcess : subProcessList) {
                Collection<FlowElement> subProcessFlowElements = subProcess.getFlowElements();
                checkBpmnNode(subProcessFlowElements, true);
            }
        }
    }

    /**
     * 校验bpmn节点是否合法
     *
     * @param flowElements 节点集合
     * @param subtask      是否子流程
     */
    private static void checkBpmnNode(Collection<FlowElement> flowElements, boolean subtask) throws ServerException {

        if (CollUtil.isEmpty(flowElements)) {
            throw new ServerException(subtask ? "子流程必须存在节点" : "必须存在节点！");
        }

        List<StartEvent> startEventList = flowElements.stream().filter(StartEvent.class::isInstance).map(StartEvent.class::cast).collect(Collectors.toList());
        if (CollUtil.isEmpty(startEventList)) {
            throw new ServerException(subtask ? "子流程必须存在开始节点" : "必须存在开始节点！");
        }

        if (startEventList.size() > 1) {
            throw new ServerException(subtask ? "子流程只能存在一个开始节点" : "只能存在一个开始节点！");
        }

        StartEvent startEvent = startEventList.get(0);
        List<SequenceFlow> outgoingFlows = startEvent.getOutgoingFlows();
        if (CollUtil.isEmpty(outgoingFlows)) {
            throw new ServerException(subtask ? "子流程流程节点为空，请至少设计一条主线流程！" : "流程节点为空，请至少设计一条主线流程！");
        }

        FlowElement targetFlowElement = outgoingFlows.get(0).getTargetFlowElement();
        if (!(targetFlowElement instanceof UserTask) && !subtask) {
            throw new ServerException("开始节点后第一个节点必须是用户任务！");
        }
        List<EndEvent> endEventList = flowElements.stream().filter(EndEvent.class::isInstance).map(EndEvent.class::cast).collect(Collectors.toList());
        if (CollUtil.isEmpty(endEventList)) {
            throw new ServerException(subtask ? "子流程必须存在结束节点！" : "必须存在结束节点！");
        }
    }

    /***
     * 流程发布前置处理
     */
    public static void preDeploy(BpmnModel bpmnModel) {
        Process mainProcess = bpmnModel.getMainProcess();
        buildUserTaskFlowElements(mainProcess.getFlowElements());
    }

    /**
     * 递归处理获取所有节点
     *
     * @param flowElements 节点信息
     */
    private static void buildUserTaskFlowElements(Collection<FlowElement> flowElements) {
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof SubProcess) {
                Collection<FlowElement> subFlowElements = ((SubProcess) flowElement).getFlowElements();
                buildUserTaskFlowElements(subFlowElements);
            } else if (flowElement instanceof UserTask userTask) {
                Map<String, List<ExtensionAttribute>> attributes = userTask.getAttributes();
                if (CollUtil.isNotEmpty(attributes.get(FlowableConstants.USER_TASK_AUTO_SKIP_KEY))) {
                    // 添加自动审批监听器
                    addTaskListener(userTask, AutoApprovalTaskListener.class);
                }
            }
        }
    }

    /**
     * 获取结束节点
     */
    public static EndEvent getEndEvent(BpmnModel model) {
        Process process = model.getMainProcess();
        // 从 flowElementList 找 endEvent
        return (EndEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof EndEvent);
    }

    /**
     * 获取流程全部用户节点
     *
     * @param processDefinitionId 流程定义id
     */
    public static List<UserTask> getUserTaskFlowElements(String processDefinitionId) {
        List<UserTask> list = new ArrayList<>();
        BpmnModel bpmnModel = PROCESS_ENGINE.getRepositoryService().getBpmnModel(processDefinitionId);
        Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
        buildUserTaskFlowElements(flowElements, list);
        return list;
    }

    /***
     * 获取流程全部用户节点
     * @param bpmnModel bpmn模型
     * @return {@link List} {@link UserTask}
     * @author rain
     */
    public static List<UserTask> getUserTaskFlowElements(BpmnModel bpmnModel) {
        List<UserTask> list = new ArrayList<>();
        Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
        buildUserTaskFlowElements(flowElements, list);
        return list;
    }

    /**
     * 递归获取所有节点
     *
     * @param flowElements 节点信息
     * @param list         集合
     */
    private static void buildUserTaskFlowElements(Collection<FlowElement> flowElements, List<UserTask> list) {
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof SubProcess) {
                Collection<FlowElement> subFlowElements = ((SubProcess) flowElement).getFlowElements();
                buildUserTaskFlowElements(subFlowElements, list);
            } else if (flowElement instanceof UserTask) {
                list.add((UserTask) flowElement);
            }
        }
    }

    /**
     * 获取流程全部节点
     *
     * @param processDefinitionId 流程定义id
     */
    public static List<FlowElement> getFlowElements(String processDefinitionId) {
        BpmnModel bpmnModel = PROCESS_ENGINE.getRepositoryService().getBpmnModel(processDefinitionId);
        List<FlowElement> list = new ArrayList<>();
        Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
        buildFlowElements(flowElements, list);
        return list;
    }

    /**
     * 递归获取所有节点
     *
     * @param flowElements 节点信息
     * @param list         集合
     */
    private static void buildFlowElements(Collection<FlowElement> flowElements, List<FlowElement> list) {
        for (FlowElement flowElement : flowElements) {
            list.add(flowElement);
            if (flowElement instanceof SubProcess) {
                Collection<FlowElement> subFlowElements = ((SubProcess) flowElement).getFlowElements();
                buildFlowElements(subFlowElements, list);
            }
        }
    }

    /**
     * 获取全部扩展信息
     *
     * @param processDefinitionId 流程定义id
     */
    public static Map<String, List<ExtensionElement>> getExtensionElements(String processDefinitionId) {
        Map<String, List<ExtensionElement>> map = new HashMap<>();
        List<FlowElement> flowElements = getFlowElements(processDefinitionId);
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof UserTask && CollUtil.isNotEmpty(flowElement.getExtensionElements())) {
                map.putAll(flowElement.getExtensionElements());
            }
        }
        return map;
    }

    /**
     * 获取某个节点的扩展信息
     *
     * @param processDefinitionId 流程定义id
     * @param flowElementId       节点id
     */
    public static Map<String, List<ExtensionElement>> getExtensionElement(String processDefinitionId, String flowElementId) {
        BpmnModel bpmnModel = PROCESS_ENGINE.getRepositoryService().getBpmnModel(processDefinitionId);
        Process process = bpmnModel.getMainProcess();
        FlowElement flowElement = process.getFlowElement(flowElementId);
        return flowElement.getExtensionElements();
    }

    public static UserTask getUserTask(String processDefinitionId, String taskDefinitionKey) {
        BpmnModel bpmnModel = PROCESS_ENGINE.getRepositoryService().getBpmnModel(processDefinitionId);
        FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey);
        return Optional.ofNullable(flowElement).filter(UserTask.class::isInstance).map(UserTask.class::cast).orElse(null);
    }

    /**
     * 判断当前节点是否为用户任务
     *
     * @param processDefinitionId 流程定义id
     * @param taskDefinitionKey   流程定义id
     */
    public static boolean isUserTask(String processDefinitionId, String taskDefinitionKey) {
        BpmnModel bpmnModel = PROCESS_ENGINE.getRepositoryService().getBpmnModel(processDefinitionId);
        FlowNode flowNode = (FlowNode) bpmnModel.getFlowElement(taskDefinitionKey);
        return flowNode instanceof UserTask;
    }

    /**
     * 获取用户任务的按钮配置
     *
     * @param processDefinitionId 流程定义ID
     * @param taskDefinitionKey   任务定义Key
     * @return 按钮配置信息
     */
    public static List<ButtonConfig> getTaskButtonConfig(String processDefinitionId, String taskDefinitionKey) {
        Map<String, List<ExtensionElement>> extensions = getExtensionElement(processDefinitionId, taskDefinitionKey);
        if (extensions == null || !extensions.containsKey("buttonConfig")) {
            return CollUtil.newArrayList();
        }
        List<ExtensionElement> buttonConfigs = extensions.get("buttonConfig");
        if (buttonConfigs == null || buttonConfigs.isEmpty()) {
            return CollUtil.newArrayList();
        }
        return buildButtonConfig(buttonConfigs);
    }

    /**
     * 获取所有用户任务的按钮配置
     *
     * @param processDefinitionId 流程定义ID
     * @return 所有任务的按钮配置
     */
    public static Map<String, List<ButtonConfig>> getAllTaskButtonConfigs(String processDefinitionId) {
        Map<String, List<ButtonConfig>> result = new HashMap<>();
        List<UserTask> userTasks = getUserTaskFlowElements(processDefinitionId);
        // 遍历所有节点
        for (UserTask userTask : userTasks) {
            // 获取扩展元素
            Map<String, List<ExtensionElement>> extensions = userTask.getExtensionElements();
            List<ExtensionElement> buttonConfigs = extensions != null ? extensions.get("buttonConfig") : null;
            if (buttonConfigs != null && !buttonConfigs.isEmpty()) {
                result.put(userTask.getId(), buildButtonConfig(buttonConfigs));
            }
        }
        return result;
    }

    private static List<ButtonConfig> buildButtonConfig(List<ExtensionElement> buttonConfigs) {
        List<ButtonConfig> data = new ArrayList<>();
        buttonConfigs.forEach(t -> {
            ButtonConfig buttonConfig = new ButtonConfig();
            buttonConfig.setId(t.getAttributeValue(null, "id"));
            buttonConfig.setName(t.getAttributeValue(null, "name"));
            buttonConfig.setType(t.getAttributeValue(null, "type"));
            buttonConfig.setSort(Convert.toInt(t.getAttributeValue(null, "sort")));
            buttonConfig.setEnabled(Convert.toBool(t.getAttributeValue(null, "enabled")));
            data.add(buttonConfig);
        });
        return data;
    }

    /**
     * 获取申请人节点
     *
     * @param processDefinitionId 流程定义id
     * @return 结果
     */
    public static UserTask getApplyUserTask(String processDefinitionId) {
        BpmnModel bpmnModel = PROCESS_ENGINE.getRepositoryService().getBpmnModel(processDefinitionId);
        return getApplyUserTask(bpmnModel);
    }

    /**
     * 获取申请人节点
     *
     * @param bpmnModel 模型
     * @return 结果
     */
    public static UserTask getApplyUserTask(BpmnModel bpmnModel) {
        Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
        List<StartEvent> startEventList = flowElements.stream().filter(StartEvent.class::isInstance).map(StartEvent.class::cast).toList();
        StartEvent startEvent = startEventList.get(0);
        List<SequenceFlow> outgoingFlows = startEvent.getOutgoingFlows();
        FlowElement targetFlowElement = outgoingFlows.get(0).getTargetFlowElement();
        return (UserTask) targetFlowElement;
    }

    /**
     * 用户任务添加任务监听器
     */
    public static void addTaskListener(UserTask userTask, Class<? extends TaskListener> listenerClazz) {
        // 创建任务监听器
        FlowableListener listener = new FlowableListener();
        listener.setEvent(TaskListener.EVENTNAME_CREATE);
        listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_CLASS);
        listener.setImplementation(listenerClazz.getName());
        // 添加监听器
        userTask.getTaskListeners().add(listener);
    }

    /**
     * 退回流程节点时，校验目标任务节点是否可退回
     *
     * @param sourceKey           当前任务节点 Key
     * @param targetKey           目标任务节点 key
     * @param processDefinitionId 当前流程定义 ID
     */
    public static void validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) {
        BpmnModel bpmnModel = PROCESS_ENGINE.getRepositoryService().getBpmnModel(processDefinitionId);
        Process process = bpmnModel.getMainProcess();
        // 获取当前任务节点元素
        FlowElement source = process.getFlowElement(sourceKey);
        // 获取跳转的节点元素
        FlowElement target = process.getFlowElement(targetKey);
        if (target == null) {
            throw new ServiceException("目标节点不存在");
        }
        // 只有串行可到达的目标节点，才可以退回。目标节点类似非串行、子流程则无法退回
        if (!ModelQueryUtils.isSequentialReachable(source, target, null)) {
            throw new ServiceException("退回任务失败，目标节点是在并行网关上或非同一路线上，不可跳转");
        }
    }

    //获取用户节点的扩展元素
    public static String getUserTaskExtension(FlowElement userTask, String name) {
        return userTask.getAttributeValue(BpmnModelConstants.NAMESPACE, name);
    }

    /**
     * 获取任务的所有可能的下一级任务节点
     *
     * @param taskDefinitionKey   当前任务ID
     * @param processDefinitionId 流程定义ID
     * @return 下一级任务节点信息列表
     */
    public static List<UserTask> findNextTasks(String taskDefinitionKey, String processDefinitionId) {
        // 获取流程定义模型
        BpmnModel bpmnModel = PROCESS_ENGINE.getRepositoryService().getBpmnModel(processDefinitionId);
        // 获取当前节点
        FlowElement currentElement = bpmnModel.getMainProcess().getFlowElement(taskDefinitionKey);
        // 存储下一级任务节点
        List<UserTask> nextTasks = new ArrayList<>();
        // 递归查找下一级节点
        ModelQueryUtils.findNextElements(currentElement, nextTasks, new HashSet<>());
        return nextTasks;
    }

}
